using System;

using OpenTK;
using OpenTK.Graphics;
using OpenTK.Graphics.OpenGL;
using OpenTK.Audio;
using OpenTK.Audio.OpenAL;
using OpenTK.Input;
using System.Drawing;

namespace StarterKit
{
    class Game : GameWindow
    {
        /// <summary>Creates a 800x600 window with the specified title.</summary>
        public Game()
            : base(800, 600, GraphicsMode.Default, "OpenTK Quick Start Sample")
        {
            VSync = VSyncMode.On;
        }

        /// <summary>Load resources here.</summary>
        /// <param name="e">Not used.</param>
        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);

            GL.ClearColor(1, 1, 1, 0.0f);
            GL.Enable(EnableCap.DepthTest);

            GL.Viewport(ClientRectangle.X, ClientRectangle.Y, ClientRectangle.Width, ClientRectangle.Height);

            Matrix4 projection = Matrix4.CreatePerspectiveFieldOfView((float)Math.PI / 4, Width / (float)Height, 0.1f, 64.0f);
            GL.MatrixMode(MatrixMode.Projection);
            GL.LoadMatrix(ref projection);
        }

        /// <summary>
        /// Called when your window is resized. Set your viewport here. It is also
        /// a good place to set up your projection matrix (which probably changes
        /// along when the aspect ratio of your window).
        /// </summary>
        /// <param name="e">Not used.</param>
        protected override void OnResize(EventArgs e)
        {
            base.OnResize(e);
        }

        /// <summary>
        /// Called when it is time to setup the next frame. Add you game logic here.
        /// </summary>
        /// <param name="e">Contains timing information for framerate independent logic.</param>
        protected override void OnUpdateFrame(FrameEventArgs e)
        {
            base.OnUpdateFrame(e);

            if (Keyboard[Key.Escape])
                Exit();

            if (Keyboard[Key.S])
            {
                Bitmap b = GrabScreenshot();
                b.Save("out.png");
            }

            if (Keyboard[Key.Up])
                rotateX += 1;
            if (Keyboard[Key.Down])
                rotateX -= 1;

            if (Keyboard[Key.Left])
                rotateY += 1;
            if (Keyboard[Key.Right])
                rotateY -= 1;
        }

        private double rotateX = 0;
        private double rotateY = 0;
        private double rotateZ = 0;

        public Bitmap GrabScreenshot()
        {
            Bitmap bmp = new Bitmap(this.ClientSize.Width, this.ClientSize.Height);
            System.Drawing.Imaging.BitmapData data = bmp.LockBits(this.ClientRectangle, System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
            GL.ReadPixels(0, 0, this.ClientSize.Width, this.ClientSize.Height, PixelFormat.Bgr, PixelType.UnsignedByte, data.Scan0);
            bmp.UnlockBits(data);
            bmp.RotateFlip(RotateFlipType.RotateNoneFlipY);
            return bmp;
        }

        private static int frame = 0;
        protected override void OnRenderFrame(FrameEventArgs e)
        {
            base.OnRenderFrame(e);

            frame++;

            GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);

            Matrix4 modelview = Matrix4.LookAt(new Vector3(10f, 10f, 0.0f), Vector3.Zero, Vector3.UnitY);
            GL.MatrixMode(MatrixMode.Modelview);
            GL.LoadMatrix(ref modelview);

            GL.Rotate(rotateX, Vector3d.UnitX);
            GL.Rotate(rotateY, Vector3d.UnitY);
            GL.Rotate(rotateZ, Vector3d.UnitZ);

            // Cubes
            drawCube(new Vector3(-2, 2, 0), Color.Blue);
            drawCube(new Vector3(-2, -2, 0), Color.Red);
            drawCube(new Vector3(0, 0, 0), Color.Yellow);
            drawCube(new Vector3(2, 2, 0), Color.Green);
            drawCube(new Vector3(2, -2, 0), Color.Cyan);

            // Axes
            // X
            GL.Begin(BeginMode.Lines);
            GL.Color3(Color.Red);
            foreach (Vector3 v in new Vector3[] { Vector3.Zero, Vector3.UnitX * 10 })
                GL.Vertex3(v);
            GL.End();

            // Y
            GL.Begin(BeginMode.Lines);
            GL.Color3(Color.Blue);
            foreach (Vector3 v in new Vector3[] { Vector3.Zero, Vector3.UnitY * 10 })
                GL.Vertex3(v);
            GL.End();

            // Z
            GL.Begin(BeginMode.Lines);
            GL.Color3(Color.Green);
            foreach (Vector3 v in new Vector3[] { Vector3.Zero, Vector3.UnitZ * 10 })
                GL.Vertex3(v);
            GL.End();

            SwapBuffers();
        }

        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            // The 'using' idiom guarantees proper resource cleanup.
            // We request 30 UpdateFrame events per second, and unlimited
            // RenderFrame events (as fast as the computer can handle).
            using (Game game = new Game())
            {
                game.Visible = false;
                game.Run(30.0);
            }
        }

        static void drawCube(Vector3 offset, Color color)
        {
            var cubeIndices = new int[] 
                        { 
                                // front face
                                0, 1, 2, 2, 3, 0, 
                                // top face
                                3, 2, 6, 6, 7, 3, 
                                // back face
                                7, 6, 5, 5, 4, 7, 
                                // left face
                                4, 0, 3, 3, 7, 4, 
                                // bottom face
                                0, 1, 5, 5, 4, 0,
                                // right face
                                1, 5, 6, 6, 2, 1
                        };

            var cubeVertices = new Vector3[] 
                        {
                                offset + new Vector3 (-1.0f, -1.0f, 1.0f),
                                offset + new Vector3 (1.0f, -1.0f, 1.0f),
                                offset + new Vector3 (1.0f, 1.0f, 1.0f), 
                                offset + new Vector3 (-1.0f, 1.0f, 1.0f), 
                                offset + new Vector3 (-1.0f, -1.0f, -1.0f), 
                                offset + new Vector3 (1.0f, -1.0f, -1.0f), 
                                offset + new Vector3 (1.0f, 1.0f, -1.0f), 
                                offset + new Vector3 (-1.0f, 1.0f, -1.0f) 
                        };

            GL.Begin(BeginMode.Triangles);
            GL.Color3(color);
            foreach (int index in cubeIndices)
                GL.Vertex3(cubeVertices[index]);
            GL.End();
        }
    }
}